/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package kpsmart.model.business;

import java.util.*;
import kpsmart.model.events.Event;
import kpsmart.model.events.Events;
import kpsmart.model.events.MailType;
import kpsmart.model.routes.*;

/**
 * Class to hold all business figures until a certain date. All business figures relate to the events that are supplied in the constructor
 * 
 * @author Oliver Behncke
 */
public class KeyBusinessFigures {
    
    // the main list of all events relevant to KBG
    private List<Event> events;
    
    /**
     * Some maps stored for calculation purposes
     */
    private Map<MailType,ChainedRoute> usedRoutes;
    private Map<MailType,Integer> deliveryTimes;
    private Map<MailType,Integer> prices;
    private Map<MailType,Integer> costs;
    private Map<RouteTuple,Double> criticalRoutes;
    private Map<RouteTuple,Double> averageDeliveryTimes;
    private Map<RouteTuple,Integer> mailPerRoute;
    private Map<RouteTuple,Integer> weightPerRoute;
    private Map<RouteTuple,Integer> volumePerRoute;
    
    
    
    private KeyBusinessFigures(List<Event> events){
        this.events=new ArrayList<Event>(events);
        this.usedRoutes=Routes.getUsedRoutes(events);
        this.deliveryTimes=Routes.getDeliveryTimes(usedRoutes);
        this.prices=Routes.getUsedPrices(events, usedRoutes);
        this.costs=Routes.getCostsOfMails(usedRoutes);
        this.criticalRoutes=calcCriticalRoutes(prices,costs);
        this.averageDeliveryTimes=calcAverageDeliveryTimes(deliveryTimes);
        Map<RouteTuple,List<MailType>> mails=mailtypePerRoute(Events.getMailTypes(events));
        this.mailPerRoute=calcMailPerRoute(mails);
        this.weightPerRoute=calcWeightPerRoute(mails);
        this.volumePerRoute=calcVolumePerRoute(mails);
        
    }
    
    /**
     * Get expenditure
     * @return expenditure
     */
    public int getExpenditure(){
        int result=0;
        for(Integer i:costs.values()) result+=i;
        return result;
    }
    
    /**
     * Get total revenue
     * @return revenue
     */
    public int getRevenue(){
        int result=0;
        for(Integer i:prices.values()) result+=i;
        return result;
    }
    
    /**
     * Get total number of mail events
     * @return total number as int
     */
    public int getTotalNumberOfEvents(){
        return events.size();
    }
    
    /**
     * Get total number of mail items
     * @return total number as int
     */
    public int getTotalNumberOfMailItems(){
        return Events.getMailTypes(events).size();
    }
    
    /**
     * Get total weight of mail items
     * @return total weight as int
     */
    public int getTotalWeightOfMailItems(){
        int result=0;
        for(MailType m: Events.getMailTypes(events)) result+=m.getWeight().intValue();
        return result;
    }

    /**
     * Get total volume of mail items
     * @return total volume as int
     */
    public int getTotalVolumeOfMailItems(){
        int result=0;
        for(MailType m: Events.getMailTypes(events)) result+=m.getVolume().intValue();
        return result;
    }
    
    /**
     * Get critical routes and the average loss on them as map
     * @return map
     */
    public Map<RouteTuple,Double> getCriticalRoutes(){
        return new HashMap<RouteTuple,Double>(criticalRoutes);
    }
    
    /**
     * Helper function to set the criticalRoutes variable
     * @return 
     */
    private Map<RouteTuple,Double> calcCriticalRoutes(Map<MailType,Integer> prices,Map<MailType,Integer> costs){
        Map<RouteTuple,List<Integer>> profits=new HashMap<RouteTuple,List<Integer>>();
        for(Map.Entry<MailType,Integer> e: prices.entrySet()){
            MailType m=e.getKey();
            RouteTuple tuple=RouteTuple.create(m.getFrom(), m.getTo(), m.getPriority());
            if(!profits.containsKey(tuple)) profits.put(tuple, new LinkedList<Integer>());
            profits.get(tuple).add(e.getValue()-costs.get(m));
        }
        Map<RouteTuple,Double> result=new HashMap<RouteTuple,Double>();
        for(Map.Entry<RouteTuple,List<Integer>> entry: profits.entrySet()){
            List<Integer> value=entry.getValue();
            int sum=0;
            for(Integer i: value){
                sum+=i;
            }
            if(sum<0) result.put(entry.getKey(),Double.valueOf(sum)/value.size());
        }
        return result;
    }
    
    /**
     * Get the average delivery times per route as map
     * @return map
     */
    public Map<RouteTuple,Double> getAverageDeliveryTimes(){
        return new HashMap<RouteTuple,Double>(averageDeliveryTimes);
    }
    
    /**
     * Private helper function to calculate the average delivery times per route
     * @param deliveryTimes as map
     * @return map
     */
    private Map<RouteTuple,Double> calcAverageDeliveryTimes(Map<MailType,Integer> deliveryTimes){
        Map<RouteTuple,List<Integer>> durations=new HashMap<RouteTuple,List<Integer>>();
        for(Map.Entry<MailType,Integer> e: deliveryTimes.entrySet()){
            MailType m=e.getKey();
            RouteTuple tuple=RouteTuple.create(m.getFrom(), m.getTo(), m.getPriority());
            if(!durations.containsKey(tuple)) durations.put(tuple, new LinkedList<Integer>());
            durations.get(tuple).add(e.getValue());
        }
        Map<RouteTuple,Double> result=new HashMap<RouteTuple,Double>();
        for(Map.Entry<RouteTuple,List<Integer>> entry: durations.entrySet()){
            List<Integer> value=entry.getValue();
            int sum=0;
            for(Integer i: value){
                sum+=i;
            }
            result.put(entry.getKey(),Double.valueOf(sum)/value.size());
        }
        return result;
    }
    
    /**
     * Helper function to create a map of mail items per route
     * @param mails
     * @return map
     */
    private Map<RouteTuple,List<MailType>> mailtypePerRoute(List<MailType> mails){
        Map<RouteTuple,List<MailType>> result=new HashMap<RouteTuple,List<MailType>>();
        for(MailType m: mails){
            RouteTuple tuple=RouteTuple.create(m.getFrom(), m.getTo(), m.getPriority());
            if(!result.containsKey(tuple)) result.put(tuple, new LinkedList<MailType>());
            result.get(tuple).add(m);
        }
        return result;
    }
    
    /**
     * Factory method for KBF creation
     * @param events
     * @return KBF object for events.
     */
    static KeyBusinessFigures create(List<Event> events){
        return new KeyBusinessFigures(events);
    }

    /**
     * Get a map of all routes and the number of mail items on this route
     * @return map
     */
    public Map<RouteTuple,Integer> getMailPerRoute(){
        return new HashMap<RouteTuple,Integer>(mailPerRoute);
    }
    
    /**
     * Private helper function to calculate the number of mail items per route.
     * @param mails
     * @return map
     */
    private Map<RouteTuple, Integer> calcMailPerRoute(Map<RouteTuple, List<MailType>> mails) {
        Map<RouteTuple,Integer> result=new HashMap<RouteTuple,Integer>();
        for(Map.Entry<RouteTuple,List<MailType>> e: mails.entrySet()){
            result.put(e.getKey(), e.getValue().size());
        }
        return result;
    }
    
    /**
     * Get total weight of mail items per Route as map
     * @return map
     */
    public Map<RouteTuple,Integer> getWeightPerRoute(){
        return new HashMap<RouteTuple,Integer>(weightPerRoute);
    }

    /**
     * Private helper function to calculate the total weight of mails per route
     * @param mails
     * @return map
     */
    private Map<RouteTuple, Integer> calcWeightPerRoute(Map<RouteTuple, List<MailType>> mails) {
        Map<RouteTuple,Integer> result=new HashMap<RouteTuple,Integer>();
        for(Map.Entry<RouteTuple,List<MailType>> e: mails.entrySet()){
            int sum=0;
            for(MailType m: e.getValue()) sum+=m.getWeight().intValue();
            result.put(e.getKey(), sum);
        }
        return result;
    }
    
    /**
     * Get the total volume per route as map
     * @return map
     */
    public Map<RouteTuple,Integer> getVolumePerRoute(){
        return new HashMap<RouteTuple,Integer>(volumePerRoute);
    }

    /**
     * Private helper function to calculate the total volume per route
     * @param mails per Route as map
     * @return map
     */
    private Map<RouteTuple, Integer> calcVolumePerRoute(Map<RouteTuple, List<MailType>> mails) {
        Map<RouteTuple,Integer> result=new HashMap<RouteTuple,Integer>();
        for(Map.Entry<RouteTuple,List<MailType>> e: mails.entrySet()){
            int sum=0;
            for(MailType m: e.getValue()) sum+=m.getVolume().intValue();
            result.put(e.getKey(), sum);
        }
        return result;    
    }
}
